#define VIDEO_BUFFER_BASE 0xB8000   // video frame buffer
#define CHARS_PER_LINE 80           // how many columns in our basic shell
#define ROWS_PER_SCREEN 25          // how many rows in our basic shell
#define INTERRUPT_DESCRIPTOR_TABLE_BASE 0x00002000  //the location of our IDT table

#define PIC1		0x20		/* IO base address for master PIC */
#define PIC2		0xA0		/* IO base address for slave PIC */
#define PIC1_COMMAND	PIC1
#define PIC1_DATA	(PIC1+1)
#define PIC2_COMMAND	PIC2
#define PIC2_DATA	(PIC2+1)

/* reinitialize the PIC controllers, giving them specified vector offsets
   rather than 8 and 70, as configured by default */
 
#define ICW1_ICW4	0x01		/* ICW4 (not) needed */
#define ICW1_SINGLE	0x02		/* Single (cascade) mode */
#define ICW1_INTERVAL4	0x04		/* Call address interval 4 (8) */
#define ICW1_LEVEL	0x08		/* Level triggered (edge) mode */
#define ICW1_INIT	0x10		/* Initialization - required! */
 
#define ICW4_8086	0x01		/* 8086/88 (MCS-80/85) mode */
#define ICW4_AUTO	0x02		/* Auto (normal) EOI */
#define ICW4_BUF_SLAVE	0x08		/* Buffered mode/slave */
#define ICW4_BUF_MASTER	0x0C		/* Buffered mode/master */
#define ICW4_SFNM	0x10		/* Special fully nested (not) */
 

/////////////////////////////////////////
// Type definitions
////////////////////////////////////////

typedef unsigned  short int uint16_t;
typedef unsigned char uint8_t;
typedef long int uint32_t;
typedef unsigned char byte;

/*
 * Interrupt Descriptor Table
 *


 */
struct IDT 
{
   uint16_t offset_1; // offset bits 0..15
   uint16_t selector; // a code segment selector in GDT or LDT
   uint8_t zero;      // unused, set to 0
   uint8_t type_attr; // type and attributes, see above
   uint16_t offset_2; // offset bits 16..31
};

struct IDT_DESCRIPTION_STRUCTURE
{
    uint16_t length;
    void *address;
};


/////////////////////////////////////////
// Function prototypes
/////////////////////////////////////////
void ClearScreen();
void PrintChar(unsigned char c, unsigned char color, unsigned int x, unsigned int y);
void ConsoleOut(char *szText);
void MoveCursor(int row, int col);
void Scroll();
char *UIntConvertToString(unsigned int number);
char *IntConvertToString(int number);
char *ConvertToHex(unsigned int number);
static inline void outb( unsigned short port, unsigned char val );
static inline void lidt( void * base, unsigned int size );
static inline void io_wait( void );
static inline void enable_interrupts();
static inline unsigned char inb( unsigned short port );
static inline void test_int();
void InitIDT();
void PIC_remap(int offset1, int offset2);
void KernelMain(void* mbd, unsigned int isrAddress);
void keyboard_init();

//void GenericInterruptHandler(unsigned int number, unsigned int error);

void IRQ_set_mask(unsigned char IRQline);
void IRQ_clear_mask(unsigned char IRQline);


///////////////////////////////////////////
// Global variables
///////////////////////////////////////////
int g_cursorX = 0;                      // Cursor X position
int g_cursorY = ROWS_PER_SCREEN - 1;    // Cursor Y position
char *g_strInteger = "           ";     // 10 digits plus room for a - sign
char *g_strHexidecimal = "          ";  // 8 digits plus 0x prefix
char *g_strNumbers = "0123456789";      // All vaild numbers
char *g_hexNumbers = "0123456789ABCDEF";// All valid hex numbers
unsigned int g_isrAddress = 0;                 // Address of the generic isr handler
struct IDT g_idtArray[256];
struct IDT_DESCRIPTION_STRUCTURE g_idStructure;
void (*MyFunction)();

/*
 * Entry point for the kernel
 * mdb - unused
 * magic - unused
 *
 */
void kmain( void* mbd, unsigned int magic )
{
    KernelMain(mbd, magic);
}

// Generic Interrupt Handler
//
// Handles all interrupts
//
// error - the error code passed to the ISR
// number - the number of the interrupt
//
void GenericInterruptHandler(unsigned int error, unsigned int number)
{

    ConsoleOut("Interrupt Received: ");
    char *scanCodeStr = ConvertToHex(number);
    ConsoleOut(scanCodeStr);
    ConsoleOut("\n");

    if (error > 0)
    {
        ConsoleOut("Error: ");
        char *scanCodeStr = ConvertToHex(error);
        ConsoleOut(scanCodeStr);
        ConsoleOut("\n");
    }

    if (number == 0x21)
    {
        unsigned char letter = inb(0x60);
        char *scanCodeStr = ConvertToHex((unsigned int)letter);
        ConsoleOut(scanCodeStr);
        ConsoleOut("\n");

        outb(0x20, 0x20);
    }
}

//
// Kernel Main
//
// isrAddress - The pointer to the function that the isr handler in asm will call
//
void KernelMain(void* mbd, unsigned int isrAddress)
{
    //isrAddress = 0;
    mbd = 0;

    ClearScreen();
    ConsoleOut("Kernel of D00m is now running...\n");

    unsigned int *newAddress = (unsigned int *)isrAddress;
    *newAddress = (unsigned int)*GenericInterruptHandler;

    PIC_remap(0x20,0x28);

    IRQ_set_mask(0);
    IRQ_clear_mask(1);

    asm("sti"); //Enable interrupts

    ConsoleOut("Clearing Keyboard Buffer");
    keyboard_init();

    ConsoleOut("Starting while loop\n");

    while(1)
    {
    }
}

// 
// Converts an unsinged int to a string
//
char *UIntConvertToString(unsigned int number)
{
    int integerIndex = 11;
    int remainder = 0;

    while (number > 0)
    {
        remainder = number % 10;
        g_strInteger[integerIndex - 1] = g_strNumbers[remainder];
        integerIndex--;
        number = number / 10;
    }

    return g_strInteger + integerIndex;

}

//
// Converts and int to a string
//
char *IntConvertToString(int number)
{
    int bIsNegative = number < 0;
    int integerIndex = 11;
    int remainder = 0;

    if (bIsNegative)
    {
        number = number * -1;
    }

    while (number > 0)
    {
        remainder = number % 10;
        g_strInteger[integerIndex - 1] = g_strNumbers[remainder];
        integerIndex--;
        number = number / 10;
    }

    if (bIsNegative)
    {
        g_strInteger[integerIndex - 1] = '-';
        integerIndex--;
    }

    return g_strInteger + integerIndex;
}

//
// Convers an unsigned int to a hex string
//
char *ConvertToHex(unsigned int number)
{
    int hexIndex = 10;
    int remainder = 0;

    while (number > 0)
    {
        remainder = number % 16;
        g_strHexidecimal[hexIndex - 1] = g_hexNumbers[remainder];
        hexIndex--;
        number = number / 16;
    }

    g_strHexidecimal[hexIndex - 1] = 'x';
    hexIndex--;
    g_strHexidecimal[hexIndex - 1] = '0';
    hexIndex--;

    return g_strHexidecimal + hexIndex;
}

/* 
 * Prints a character to an x,y location on the screen 
 *
 * Params
 * c - the character to print to the screen
 * color - colors to print with (0x0F - black on white)
 * x - horizontal coordinate 0 is the first column on the left
 * y - vertical coordinate 0 is the top of the screen
 *
 * NOTE: x,y will wrap if it goes beyond the  maximum cols and rows
 */
void PrintChar(unsigned char c, unsigned char color, unsigned int x, unsigned int y)
{
    x = x << 1;
    y = y * CHARS_PER_LINE << 1;

    unsigned char *videoBase = (unsigned char *)VIDEO_BUFFER_BASE;
    videoBase += x + y;
    videoBase[0] = c;
    videoBase[1] = color;
}

/* 
 * Clears the screen
 */
void ClearScreen()
{
    int i = 0;
    int j = 0;

    for (i = 0; i < CHARS_PER_LINE; i++)
    {
        for (j = 0; j < ROWS_PER_SCREEN; j++)
        {
            PrintChar(' ', 0x0F, i, j);
        }
    }
}

//
// Prints the string to the console
//
// szText - zero terminated string
//
void ConsoleOut(char *szText)
{
    int charIndex = 0;

    while(1)
    {
        unsigned char character = szText[charIndex];
        if (character == 0x00)
        {
            break;
        }
        
        if ((g_cursorX > 0 && g_cursorX % CHARS_PER_LINE == 0) ||
            character == 0x0A)
        {
            Scroll();
            g_cursorX = 0;
        }
        else
        {
            PrintChar(character, 0x0F, g_cursorX, g_cursorY);
            g_cursorX++;
        }
        charIndex++;
    }
}

//
// Scroll the text up one line. The top line on the screen disappears
//
void Scroll()
{
    int i = 0;
    int row = 0;

    unsigned char* videoBuffer = (unsigned char *)VIDEO_BUFFER_BASE;

    for (row = 0; row < ROWS_PER_SCREEN; row++)
    {
        int y = row * CHARS_PER_LINE * 2;
        unsigned char* previousRow = videoBuffer + y;

        if (row < ROWS_PER_SCREEN - 1)
        {
            y = (row +1) * CHARS_PER_LINE * 2;
            unsigned char* nextrow = videoBuffer + y;

            for (i = 0; i < CHARS_PER_LINE * 2; i++)
            {
                previousRow[i] = nextrow[i];
            }
        }
        else
        {
            for (i = 0; i < CHARS_PER_LINE * 2; i += 2)
            {
                previousRow[i] = ' ';
                previousRow[i + 1] = 0x0F;
            }
        }
    }
}

/* void update_cursor(int row, int col)
  * by Dark Fiber
  */
 void MoveCursor(int row, int col)
 {
    unsigned short position=(row*80) + col;
 
    // cursor LOW port to vga INDEX register
    outb(0x3D4, 0x0F);
    outb(0x3D5, (unsigned char)(position&0xFF));
    // cursor HIGH port to vga INDEX register
    outb(0x3D4, 0x0E);
    outb(0x3D5, (unsigned char )((position>>8)&0xFF));
 }


/*
arguments:
	offset1 - vector offset for master PIC
		vectors on the master become offset1..offset1+7
	offset2 - same for slave PIC: offset2..offset2+7
*/
void PIC_remap(int offset1, int offset2)
{
	unsigned char a1, a2;
 
	a1 = inb(PIC1_DATA);                        // save masks
	a2 = inb(PIC2_DATA);
 
	outb(PIC1_COMMAND, ICW1_INIT+ICW1_ICW4);  // starts the initialization sequence
	io_wait();
	outb(PIC2_COMMAND, ICW1_INIT+ICW1_ICW4);
	io_wait();
	outb(PIC1_DATA, offset1);                 // define the PIC vectors
	io_wait();
	outb(PIC2_DATA, offset2);
	io_wait();
	outb(PIC1_DATA, 4);                       // continue initialization sequence
	io_wait();
	outb(PIC2_DATA, 2);
	io_wait();
 
	outb(PIC1_DATA, ICW4_8086);
	io_wait();
	outb(PIC2_DATA, ICW4_8086);
	io_wait();
 
	outb(PIC1_DATA, a1);   // restore saved masks.
	outb(PIC2_DATA, a2);
}

void IRQ_set_mask(unsigned char IRQline) {
    uint16_t port;
    uint8_t value;
 
    if(IRQline < 8) {
        port = PIC1_DATA;
    } else {
        port = PIC2_DATA;
        IRQline -= 8;
    }
    value = inb(port) | (1 << IRQline);
    outb(port, value);        
}
 
void IRQ_clear_mask(unsigned char IRQline) {
    uint16_t port;
    uint8_t value;
 
    if(IRQline < 8) {
        port = PIC1_DATA;
    } else {
        port = PIC2_DATA;
        IRQline -= 8;
    }
    value = inb(port) & ~(1 << IRQline);
    outb(port, value);        
}




///////////////////////////////////////////////////////////
// Inline Assembly
///////////////////////////////////////////////////////////

static inline
void test_int()
{
    asm volatile( "int $50" );
}

 static inline
void outb( unsigned short port, unsigned char val )
{
    asm volatile( "outb %0, %1"
                  : : "a"(val), "Nd"(port) );
}

static inline
void lidt( void * base, unsigned int size )
{
    unsigned int i[2];
 
    i[0] = size << 16;
    i[1] = (unsigned int)base;
    asm( "lidt (%0)"
         : : "p"(((char *) i)+2) );
}

static inline
void enable_interrupts()
{
    asm volatile ("sti");
}

static inline
void io_wait( void )
{
    // port 0x80 is used for 'checkpoints' during POST.
    // The Linux kernel seems to think it is free for use :-/
    asm volatile( "outb %%al, $0x80"
                  : : "a"(0) );
}

static inline
unsigned char inb( unsigned short port )
{
    unsigned char ret = 0;
    asm volatile ("inb %%dx,%%al":"=a" (ret):"d" (port));
    return ret;
}

void keyboard_init()
{
    /* Wait until buffer is empty */
    while (inb(0x64) & 0x01)
      inb(0x60);

    // __asm__("int $0x21"); // also possible, but not perfect.
};